package com.dragon.backstage.service.impl;

import cn.dev33.satoken.session.SaSession;
import cn.dev33.satoken.stp.SaTokenInfo;
import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.captcha.CaptchaUtil;
import cn.hutool.captcha.ShearCaptcha;
import cn.hutool.captcha.generator.MathGenerator;
import cn.hutool.http.useragent.UserAgent;
import cn.hutool.http.useragent.UserAgentUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.dragon.backstage.constant.LoginConstant;
import com.dragon.backstage.domain.param.login.LoginParam;
import com.dragon.backstage.domain.vo.login.LoginVo;
import com.dragon.backstage.domain.vo.userOnline.UserOnlineVo;
import com.dragon.backstage.service.LoginService;
import com.dragon.backstage.service.UserService;
import com.dragon.common.core.crypto.SecureUtil;
import com.dragon.common.enums.LoginDevice;
import com.dragon.common.utils.AjaxResult;
import com.dragon.common.utils.ServletUtils;
import com.dragon.common.utils.cache.RedisUtils;
import com.dragon.common.utils.ip.AddressUtils;
import com.dragon.common.utils.ip.IpUtils;
import com.dragon.framework.manager.AsyncManager;
import com.dragon.framework.manager.factory.AsyncFactory;
import com.dragon.interfaces.constant.UserConstant;
import com.dragon.interfaces.constant.UserLoginLogConstant;
import com.dragon.interfaces.entity.User;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Objects;

/**
 * 登陆服务实现类
 *
 * @author dragon
 * @version 1.0
 * @date 2024/8/1 17:38
 */
@Slf4j
@Service
public class LoginServiceImpl implements LoginService {

    @Resource
    private HttpSession httpSession;
    @Resource
    private HttpServletResponse response;

    @Resource
    private RedisUtils redisUtils;

    @Resource
    private UserService userService;


    /**
     * 图片验证码
     *
     * @throws IOException IOException
     */
    @Override
    public void captcha() throws IOException {
        // 自定义验证码内容为四则运算方式
        ShearCaptcha captcha = CaptchaUtil.createShearCaptcha(200, 45, 4, 4);
        captcha.setGenerator(new MathGenerator());

        // 输出到浏览器
        captcha.write(response.getOutputStream());
        httpSession.setAttribute(LoginConstant.CAPTCHA_SESSION_NAME, captcha);
    }

    /**
     * 用户登陆
     *
     * @param param 登陆参数
     * @return AjaxResult<LoginVo>
     */
    @Override
    public AjaxResult<LoginVo> login(LoginParam param) {
        // 登陆设备检测
        LoginDevice loginDevice = LoginDevice.getByName(param.getLoginDevice());
        if (Objects.isNull(loginDevice)) {
            return AjaxResult.failed("登陆设备不支持");
        }

        ShearCaptcha captcha = Objects.isNull(httpSession.getAttribute(LoginConstant.CAPTCHA_SESSION_NAME)) ?
            CaptchaUtil.createShearCaptcha(200, 45, 4, 4) :
            (ShearCaptcha) httpSession.getAttribute(LoginConstant.CAPTCHA_SESSION_NAME);

        if (!captcha.verify(param.getCaptchaCode())) {
            AsyncManager.me().execute(AsyncFactory.insertLoginLog(param.getAccount(), UserLoginLogConstant.STATUS_FAIL, "验证码错误"));

            return AjaxResult.failed("验证码错误");
        }
        httpSession.removeAttribute(LoginConstant.CAPTCHA_SESSION_NAME);

        User user = userService.getOne(new QueryWrapper<User>()
            .eq(User.ACCOUNT, param.getAccount())
            .eq(User.PASSWORD, SecureUtil.sha512().digestHex(param.getPassword()))
        );
        if (Objects.isNull(user)) {
            AsyncManager.me().execute(AsyncFactory.insertLoginLog(param.getAccount(), UserLoginLogConstant.STATUS_FAIL, "账号或密码错误"));

            return AjaxResult.failed("账号或密码错误");
        }
        if (UserConstant.STATUS_DISABLED.equals(user.getStatus())) {
            AsyncManager.me().execute(AsyncFactory.insertLoginLog(param.getAccount(), UserLoginLogConstant.STATUS_FAIL, "账号处于禁用状态"));

            return AjaxResult.failed("账号处于禁用状态");
        }

        // sa-token登陆
        StpUtil.login(user.getUserId(), loginDevice.getName());
        SaTokenInfo tokenInfo = StpUtil.getTokenInfo();

        final UserAgent userAgent = UserAgentUtil.parse(ServletUtils.getRequest().getHeader("User-Agent"));
        // 客户端IP
        final String ip = IpUtils.getIpAddr();

        // 获取客户端操作系统
        String os = userAgent.getOs().getName();
        String osVersion = userAgent.getOsVersion();
        // 获取客户端浏览器
        String browser = userAgent.getBrowser().getName();
        String browserVersion = userAgent.getVersion();

        // 创建tokenSession
        SaSession saSession = StpUtil.getTokenSessionByToken(tokenInfo.getTokenValue());

        saSession.set(UserOnlineVo.TOKEN_ID, tokenInfo.getTokenValue());
        saSession.set(UserOnlineVo.ACCOUNT, user.getAccount());
        saSession.set(UserOnlineVo.IP_ADDR, ip);
        saSession.set(UserOnlineVo.LOGIN_LOCATION, AddressUtils.getRealAddressByIP(ip));
        saSession.set(UserOnlineVo.LOGIN_DEVICE, tokenInfo.getLoginDevice());
        saSession.set(UserOnlineVo.BROWSER, browser + (StringUtils.isBlank(browserVersion) ? "" : " " + browserVersion));
        saSession.set(UserOnlineVo.OS, os + (StringUtils.isBlank(osVersion) ? "" : " " + osVersion));
        saSession.set(UserOnlineVo.LOGIN_TIME, LocalDateTime.now());

        LoginVo.TokenInfo tokenInfoVo = LoginVo.TokenInfo.builder().build();
        BeanUtils.copyProperties(tokenInfo, tokenInfoVo);

        // 修改用户登陆IP和登陆时间
        User userUpdate = User
            .builder()
            .loginIp(IpUtils.getIpAddr())
            .loginTime(LocalDateTime.now())
            .build();
        userService.update(userUpdate, new QueryWrapper<User>().eq(User.ACCOUNT, user.getAccount()));

        AsyncManager.me().execute(AsyncFactory.insertLoginLog(param.getAccount(), UserLoginLogConstant.STATUS_SUCCESS, "登陆成功"));

        return AjaxResult.succeedWith(LoginVo
            .builder()
            .userId(user.getUserId())
            .account(user.getAccount())
            .nickName(user.getNickName())
            .tokenInfo(tokenInfoVo)
            .build()
        );
    }

    /**
     * 注销登陆
     *
     * @return AjaxResult<Void>
     */
    @Override
    public AjaxResult<Void> logout() {
        if (!StpUtil.isLogin()) {
            return AjaxResult.failed("当前用户已注销登陆");
        }

        AsyncManager.me().execute(AsyncFactory.insertLoginLog(getAccount(), UserLoginLogConstant.STATUS_SUCCESS, "注销登陆成功"));

        StpUtil.logout(getUserId(), StpUtil.getLoginDevice());

        return AjaxResult.succeed();
    }

    /**
     * 获取登陆用户ID
     *
     * @return 用户ID
     */
    @Override
    public Long getUserId() {
        return StpUtil.getLoginIdAsLong();
    }

    /**
     * 获取登陆用户账号
     *
     * @return 账号
     */
    @Override
    public String getAccount() {
        Long userId = getUserId();

        userService.userInfoCacheInit(userId);
        String userInfoKey = String.format(LoginConstant.USER_INFO_CACHE_KEY, userId);

        return redisUtils.hGet(userInfoKey, User.ACCOUNT).toString();
    }

    /**
     * 获取登陆用户昵称
     *
     * @return 昵称
     */
    @Override
    public String getNickName() {
        Long userId = getUserId();

        userService.userInfoCacheInit(userId);
        String userInfoKey = String.format(LoginConstant.USER_INFO_CACHE_KEY, userId);

        return redisUtils.hGet(userInfoKey, User.NICK_NAME).toString();
    }

    /**
     * 获取登陆用户账号类型
     *
     * @return 账号类型
     */
    @Override
    public String getUserType() {
        Long userId = getUserId();

        userService.userInfoCacheInit(userId);
        String userInfoKey = String.format(LoginConstant.USER_INFO_CACHE_KEY, userId);

        return redisUtils.hGet(userInfoKey, User.USER_TYPE).toString();
    }

    /**
     * 获取登陆用户账号状态
     *
     * @return 账号状态
     */
    @Override
    public Byte getStatus() {
        Long userId = getUserId();

        userService.userInfoCacheInit(userId);
        String userInfoKey = String.format(LoginConstant.USER_INFO_CACHE_KEY, userId);

        return Byte.parseByte(redisUtils.hGet(userInfoKey, User.STATUS).toString());
    }

}
